/**
 * @file form
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  Component,
  PureComponent,
  PropTypes
} from 'react';
import {Renderer, render, noop, evalExpression, renderChildren, filter, buildLink} from '../renderer';
import * as cx from 'classnames';
import * as assign from 'object-assign';
import * as update from 'react-addons-update';
import {Form as FormsyForm} from 'formsy-react';
import {Tabs, Tab, Collapse} from 'react-bootstrap';
import {resolveVariable, setVariable} from '../util';
import {merge} from '../../util/helper';
import * as forEach from 'lodash/forEach';
import * as deepEqual from 'deep-equal';
import * as cloneDeep from 'lodash/cloneDeep';

import './text/text';
import '../plain';
import '../tpl';
import './checkbox/checkbox';
import './checkbox/checkboxes';
import './checkbox/switch';
import './button/button';
import './button/button-group';
import './button/toolbar';
import './select/select';
import './select/chained';
import './date/date';
import './date/date-range';
import './divider/divider';
import './static/static';
import './matrix/matrix';
import './tree/tree';
import './image/image';
import './editor/ide';
import './editor/rich-text';
import './editor/codemirror';
import './repeat/repeat';
import './sub-form/form';
import './sub-form/forms';
import './combo/combo';
import './file/file';
import './range/range';
import './table/table';
import './panel';
import './hbox';
import './grid';
import './field';
import './form-field';
import './list/list';
import './region/region';
import './map/map';
import './phone-code/phone-code';
import './module-settings/module-settings';
import './star-rate/star-rate';
import './antd-transfer/antd-transfer'


@Renderer({
  name: 'basic-form',
  desc: '...',
  test: /(?:^|\/)basic-form$/
})
export default class BasicFormRender extends PureComponent {
  // static propTypes = {
  //   className: PropTypes.string,
  //   errors: PropTypes.object,
  //   controls: PropTypes.arrayOf(PropTypes.oneOfType([
  //     PropTypes.shape({
  //       type: PropTypes.string.isRequired
  //     }),
  //
  //     PropTypes.array
  //   ])),
  //   fieldSet: PropTypes.any,
  //   tabs: PropTypes.array,
  //   horizontal: PropTypes.object,
  //   loading: PropTypes.bool,
  //   mode: PropTypes.oneOf(['normal', 'horizontal', 'inline']),
  //   data: PropTypes.object,
  //   submitText: PropTypes.string,
  //   crudRef: PropTypes.func,
  //   collapsable: PropTypes.bool,
  //   autoFocus: PropTypes.bool,
  //
  //   onChange: PropTypes.func,
  //   onInit: PropTypes.func
  //
  // };

  static defaultProps = {
    mode: 'normal', // 可选 inline, horizontal, normal

    // 当 mode 设为 horizontal 的时候, 用来决定 label 和 input 的布局方式.
    horizontal: {
      left: 'col-sm-2',
      right: 'col-sm-10',
      offset: 'col-sm-offset-2'
    },

    horizontalDeeper: {
      left: 'col-sm-4',
      right: 'col-sm-8',
      offset: 'col-sm-offset-4'
    },

    data: {},
    submitText: '提交',
    formRef: noop,
    tabClassName: 'bg-white wrapper b-l b-r b-b',
    collapsable: false,
    autoFocus: true,
    debug: false,

    attachChanged: false,
    changedKey: 'isChanged',
    changedTrueValue: true,
    changedFalseValue: false,
    submitOnChange: false
  };

  static contextTypes = {
    scoped: PropTypes.object.isRequired
  };
  submitFns = [];
  constructor(props) {
    super(props);

    const controls = props.controls ? this.buildControls(props) : null;

    this.state = {
      controls,
      data: props.data || {}
    };
    this.defaultData = {};

    this.setDefaultValue = this.setDefaultValue.bind(this);
    this.handleAction = this.handleAction.bind(this);
    this.onChange = this.onChange.bind(this);
  }

  componentWillMount() {
    const scoped = this.context.scoped;
    scoped.registerComponent(this);
  }

  componentDidMount() {
    const {elementsOnly, autoFocus} = this.props;
    let model = elementsOnly ? {} : this.form.getModel();

    // src 自动会 clone 一份， merge 表现形式跟 lodash/merge 不一样，请查看源码
    // 初始化的时候，应该也只有初始值
    let data = merge(this.defaultData, this.props.data, model);

    if (this.props.onInit) {
      this.props.onInit(data);
    }

    if (autoFocus && this.form && this.form.inputs && this.form.inputs.length) {
      let formItem = this.form.inputs[0];
      formItem && formItem.focus && setTimeout(() => {
        formItem.focus();
      }, 500);
    }
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;

    if (props.errors !== nextProps.errors && nextProps.errors) {
      this.form.updateInputsWithError(nextProps.errors);
    }

    if (props.controls !== nextProps.controls) {
      this.setState({
        controls: this.buildControls(nextProps)
      });
    }

    if(props.data !== nextProps.data && nextProps.data !== this.state.data) {
      this.setState({
        data: assign({}, this.state.data, nextProps.data)
      });
    }
  }

  componentWillUnmount() {
    const scoped = this.context.scoped;
    scoped.unRegisterComponent(this);
    this.submitFns = null;
  }

  setDefaultValue(name, value) {
    this.defaultData = setVariable(name, value, this.defaultData);
  }

  handleAction(action) {
    const {
      onRequestAction
    } = this.props;
    const data = this.state.data;
    const args = [].slice.call(arguments, 0);

    if ((action.actionType === 'url' || action.url || action.href) &&  action.type !== 'submit') {
      const mergedData = assign({}, this.props.data, data);
      const fn = action.blank === false ? () => location.href = buildLink(action.url || action.href, mergedData) : () => window.open(buildLink(action.url || action.href, mergedData));
      return action.confirmText ? confirm(action.confirmText).then(fn) : fn();
    } else {
      onRequestAction && onRequestAction(...args);
    }
  }

  buildControls(props) {
    let mapping = field => Array.isArray(field) ? field.map(mapping) : assign({}, field);
    let controls = props.controls || [];
    let controls = controls.map(mapping);

    if (props.mode === 'inline' && props.submitText) {
      controls.push({
        type: 'submit',
        label: props.submitText,
        primary: true
      });
    }

    return controls;
  }

  submit() {
    this.form && this.form.submit();
  }

  reset() {
    this.form && this.form.reset();
  }

  renderChild(path, config, props = {}, $key) {
    const {
      mode,
      horizontal,
      horizontalDeeper,
      onRequestAction,
      locals,
      loading,
      loadingField,
      disabled,
      readOnly,
      isReady,
      setDefaultValue,
      onNotify
    } = this.props;
    const data = this.state.data;

    let value = resolveVariable(config.name, data);

    if (typeof value === 'undefined') {
      value = resolveVariable(config.name, locals);
    }

    if (typeof value === 'undefined' && config.hasOwnProperty('value')) {
      value = config.value;
    }

    const defaultProps = {
      formMode: mode,
      formMode2: config.formMode,
      formHorizontal: props.depth > 1 && !config.inline ? horizontalDeeper : horizontal,
      onRequestAction: this.handleAction,
      disabled: loadingField !== 'fetching' && loading || config.disabled || disabled,
      readOnly,
      data,
      isReady,
      locals,
      detectPropName: 'data',
      value,
      setDefaultValue: setDefaultValue || this.setDefaultValue,
      onNotify
    };

    if (config.name && config.hasOwnProperty('value')) {
      setDefaultValue ? setDefaultValue(config.name, config.value) : this.setDefaultValue(config.name, config.value);
    }
    return render(path, config, assign(defaultProps, props), $key);
  }

  renderFormItem(control, key, depth, className, $key, props = {}) {
    const {
      $path
    } = this.props;

    return this.renderChild(`${$path}/form-item`, control, assign(props, {
      key,
      depth,
      className: cx(className, control.className) || undefined,// 设置 undefined 为了能启用默认值。
    }), $key);
  }

  renderControls(controls, WrapperComponent = '', props={}, depth = 1, childClassName = '') {
    controls = controls || [];
    const count = controls.length;
    const {
      mode,
      $key
    } = this.props;
    return controls.map((control, index) => {
      let child;

      if (Array.isArray(control)) {
        if (mode === 'inline') {
          child = (
            <div className="form-row" key={`${control.name}-${index}`}>
              {this.renderControls(control, '', {}, depth + 1)}
            </div>
          );
        } else {
          child = (
            <div className={cx("form-group", {
              row: !control || !control[0] || !control[0].inline
            })} key={`${control.name}-${index}`}>
              {this.renderControls(control, 'div', {}, depth + 1, 'm-b-none')}
            </div>
          );
        }

        // if (WrapperComponent && !control.inline) {
        //   child = (
        //     <WrapperComponent
        //       {...assign(props, {
        //         className: `col-xs-${Math.round(12 / count)}`,
        //         key: index
        //       })}
        //     >
        //       {child}
        //     </WrapperComponent>
        //   );
        // } else if (control.inline && depth === 1) {
        //   child = (
        //     <div
        //       {...assign(props, {
        //         className: `form-group`,
        //         key: index
        //       })}
        //     >
        //       {child}
        //     </div>
        //   );
        // }
      } else {
        const otherProps = {
          key: `${control.name}-${index}`,
          wrappedProps: {
            key: `${control.name}-${index}`
          }
        };

        if (WrapperComponent && !control.inline) {
          otherProps.wrappedComponent = WrapperComponent;
          otherProps.wrappedProps = assign({}, props, {
            className: control.wrapperClassName || `col-sm-${Math.round(12 / count)}`,
            key: `${control.name}-${index}`
          });
        } else if (control.inline && depth === 1) {
          otherProps.wrappedComponent = 'div';
          otherProps.wrappedProps = assign({}, props, {
            className: 'form-group',
            key: `${control.name}-${index}`
          });
        }
        child = this.renderFormItem(control, `${control.name}-${index}`, depth, childClassName, `${$key}.controls.${index}`, otherProps);
      }

      return child;
    });
  }

  renderTabs(tabs) {
    const {
      tabClassName
    } = this.props;
    const data = this.state.data;


    return (
      <Tabs defaultActiveKey={0} id={`form-tabs`} >
        {tabs.map(({controls, tabs, fieldSet, title, visibleOn, hiddenOn, icon}, key) => {
          if (visibleOn && !evalExpression(visibleOn, data)) {
            return null;
          } else if (hiddenOn && evalExpression(hiddenOn, data)) {
            return null;
          }

          return (
            <Tab eventKey={key} title={ icon ? (<div><i className={icon} /> {title}</div>) : title} key={key}>
              <div className={tabClassName}>
                {this.renderFormItems({
                  controls,
                  tabs,
                  fieldSet
                })}
              </div>
            </Tab>
          );
        })}
      </Tabs>
    );
  }

  renderFiledSet(fieldSet, otherProps = {}) {
    const {
      fieldSetClassName,
      collapsable
    } = this.props;
    const data = this.state.data;

    return (
      <FieldSetGroup
        fieldSet={fieldSet}
        fieldSetClassName={otherProps.fieldSetClassName || fieldSetClassName}
        collapsable={otherProps.collapsable || collapsable}
        data={data}
        renderBody={item => this.renderFormItems(item)}
      />
    );
  }

  onChange(model, isChanged) {
    // src 自动会 clone 一份， merge 表现形式跟 lodash/merge 不一样，请查看源码
    let newData = merge(this.state.data, model);

    const {
      onChange,
      submitOnChange,
      readOnly
    } = this.props;

    const changed = !deepEqual(this.state.data, newData);

    if (!isChanged || !changed || readOnly) {
      return;
    }

    onChange && onChange(newData);
    this.setState({
      data: newData
    });
    if (submitOnChange) {
      this.form.submit();
    }
  }

  renderFormItems(schemas) {
    return schemas.tabs ? this.renderTabs(schemas.tabs, schemas) : schemas.fieldSet ?
      this.renderFiledSet(schemas.fieldSet, schemas) : this.renderControls(schemas.controls);
  }

  render() {
    const {
      className,
      mode,
      formRef,
      onSubmit,
      debug,
      tabs,
      onInvalidSubmit,
      loading,
      elementsOnly,
      $path,

      attachChanged,
      changedKey,
      changedTrueValue,
      changedFalseValue,
      disabled,
      fieldSet
    } = this.props;
    const controls = this.state.controls;
    const body = this.renderFormItems({
      tabs,
      fieldSet,
      controls
    });
    const formsyProps = {
      attachChanged,
      changedKey,
      changedTrueValue,
      changedFalseValue
    };

    if (elementsOnly) {
      return (
        <div className={cx("form", className, {
          'form-inline': mode === 'inline',
          'form-horizontal': mode === 'horizontal',
          'form-normal': mode === 'normal'
        })}

        >
          {body}
          {loading && (
            <div className="form-spinner">
              <i className="fa fa-spinner fa-spin fa-3x fa-fw text-white" />
            </div>
          )}
        </div>
      );
    }



    return (
      <FormsyForm
        ref={form => {formRef(this.form = form)}}
        className={cx("form", className, {
          'form-inline': mode === 'inline',
          'form-horizontal': mode === 'horizontal',
          'form-normal': mode === 'normal'
        })}
        onValidSubmit={disabled ? null : onSubmit}
        onInvalidSubmit={onInvalidSubmit}
        onChange={this.onChange}
        noValidate
      >
        {body}
        {debug ? (renderChildren($path, '<pre><code><%= html(json(data)) %></code></pre>', {data: this.state.data})) : null}
        {loading && (
          <div className="form-spinner">
            <i className="fa fa-spinner fa-spin fa-3x fa-fw text-white" />
          </div>
        )}
      </FormsyForm>
    );
  }
}

class FieldSetGroup extends PureComponent {

  constructor(props) {
    super(props);

    const fieldSet = props.fieldSet ? this.buildFieldSet(props) : null;

    this.state = {
      fieldSet
    };

    this.toggleFieldSetCollapsed = this.toggleFieldSetCollapsed.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;

    if (props.fieldSet !== nextProps.fieldSet) {
      this.setState({
        fieldSet: this.buildFieldSet(nextProps)
      });
    }
  }

  toggleFieldSetCollapsed(key) {
    const fieldSet = this.state.fieldSet;
    const item = fieldSet[key];

    return this.setState({
      fieldSet: update(fieldSet, {
        $splice: [
          [key, 1, assign({}, item, {
            collapsed: !item.collapsed
          })]
        ]
      })
    })
  }

  buildFieldSet(props) {
    let fileSet = props.fieldSet;

    if (fileSet && !Array.isArray(fileSet)) {
      fileSet = [fileSet];
    }

    return fileSet ? fileSet.map(item => assign({}, item)) : null;
  }

  render() {
    const {
      fieldSetClassName,
      collapsable,
      data,
      renderBody
    } = this.props;
    let fieldSet = this.state.fieldSet;

    if (fieldSet && !Array.isArray(fieldSet)) {
      fieldSet = [fieldSet];
    } else if (!fieldSet) {
      return null;
    }

    return (
      <div className={fieldSetClassName}>
        {fieldSet.map((item, key) => {
          const {hiddenOn, visibleOn} = item;

          if (visibleOn && !evalExpression(visibleOn, data)) {
            return null;
          } else if (hiddenOn && evalExpression(hiddenOn, data)) {
            return null;
          }

          return (
            <fieldset key={key} className={item.className}>
              {(item.title || item.legend) && (
                <legend onClick={() => this.toggleFieldSetCollapsed(key)}>
                  {filter(item.title || item.legend, data)}
                  {item.subTitle ? (<span className="m-l-lg text-base">{item.subTitle}</span>) : null}
                  {collapsable && (
                    <a
                      className="btn btn-link btn-sm"
                    >
                      <i className={cx('fa', item.collapsed ? 'fa-angle-right' : 'fa-angle-down')} />
                    </a>
                  )}
                </legend>
              )}
              {collapsable ? (
                <Collapse in={!item.collapsed}>
                  <div className={item.bodyClassName}>
                    {renderBody(item)}
                  </div>
                </Collapse>
              ) : renderBody(item)}
            </fieldset>
          );
        })}
      </div>
    );
  }
}
